PHP单元测试框架 PHPUnit 入门

单元测试非常之重要,好的项目,测试代码比项目代码还多,即便在开发阶段不写单元测试,在项目上线后,功能迭代的时候如果项目很大,小的单元测试可以节省繁琐的深度测试的时间。

这篇文章旨在入门 phpunit,虽然大部分框架都已经集成了 phpunit 来作为单元测试,但是你真的知道这里面的过程和细节吗?

安装PHPUnit

官网:http://www.phpunit.cn/

composer global require phpunit/phpunit

可以看到 composer 会提示将 phpunit 安装在哪个目录了。

D:\composer\vendor\bin 添加到环境变量中,因为 phpunit 是一个命令。

查看是否安装成功:phpunit --version

PHP version 7.2.1

phpunit version 8.5.14

示例

项目结构,使用的是官网的例子。

├── phpunit.xml
├── src
│   └── Email.php
└── tests
    └── EmailTest.php

Email.php

<?php 

class Email
{
    private $email;

    private function __construct(string $email)
    {
        $this->ensureIsValidEmail($email);

        $this->email = $email;
    }

    public static function fromString(string $email): self
    {
        return new self($email);
    }

    public function __toString(): string
    {
        return $this->email;
    }

    private function ensureIsValidEmail(string $email): void
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException(
                sprintf(
                    '"%s" is not a valid email address',
                    $email
                )
            );
        }
    }
}

EmailTest.php

<?php 

use PHPUnit\Framework\TestCase;

include_once('D:/dev/php/magook/trunk/server/phpunit-test/src/Email.php');

class EmailTest extends TestCase
{
    public function testCanBeCreatedFromValidEmailAddress(): void
    {
        $this->assertInstanceOf(
            Email::class,
            Email::fromString('user@example.com')
        );
    }

    public function testCannotBeCreatedFromInvalidEmailAddress(): void
    {
        $this->expectException(InvalidArgumentException::class);

        Email::fromString('invalid');
    }

    public function testCanBeUsedAsString(): void
    {
        $this->assertEquals(
            'user@example.com',
            Email::fromString('user@example.com')
        );
    }
}

进入项目根目录

> phpunit tests/EmailTest

PHPUnit 8.5.14 by Sebastian Bergmann and contributors.

Warning:       Invocation with class name is deprecated

...                                                                 3 / 3 (100%)

Time: 1.32 seconds, Memory: 4.00 MB

OK (3 tests, 3 assertions)
说明
  1. phpunit 命令后面跟的是测试文件的名字。
  2. phpunit 是一个测试框架,它的原理是将要测试的文件加载到 phpunit 框架中并运行,所以 EmailTest.php文件中加载的类PHPUnit\Framework\TestCase不是在本项目中,而是在 phpunit 框架中的。为了编码方便,很多框架都会将 phpunit 加载到自己的 vendor中。
  3. 至于Warning: Invocation with class name is deprecated,暂时可以忽略。
使用 composer 实现 autoload 自动加载

在根目录下新建 composer.json

{
    "autoload": {
        "classmap": [
            "src/"
        ]
    },
    "require-dev": {
        "phpunit/phpunit": "^8"
    }
}

然后运行 composer install

然后修改代码

Email.php

<?php 

namespace src;

use InvalidArgumentException;

......

EmailTest.php

<?php 

use PHPUnit\Framework\TestCase;
use src\Email;

// include_once('D:/dev/php/magook/trunk/server/phpunit-test/src/Email.php');

......

好了,我们来执行测试:phpunit tests/EmailTest

报错:Error: Class 'src\Email' not found,这是为什么呢?还是因为 phpunit 的运行原理,我们需要告诉它此项目的autoload方法。例如 Laravel, TP 这样的集成框架,不仅需要自动加载,还需要一些初始化操作,这些都需要告诉 phpunit。

修改命令

>phpunit --bootstrap vendor/autoload.php tests/EmailTest

PHPUnit 8.5.14 by Sebastian Bergmann and contributors.

Warning:       Invocation with class name is deprecated

...                                                                 3 / 3 (100%)

Time: 309 ms, Memory: 4.00 MB

OK (3 tests, 3 assertions)

为了简化命令,phpunit 允许你在根目录下的 phpunit.xml文件里来配置你的启动设置。

phpunit.xml

<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php" 
         colors="true">
    <testsuites>
        <testsuite name="Application Test Suite">
            <directory suffix="Test.php">tests</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">./app</directory>
        </whitelist>
    </filter>
</phpunit>

执行测试命令

在这里插入图片描述

正常运行,并且有彩色输出了。

为了加深印象,我们来对比一下 Laravel 框架的 phpunit.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="bootstrap/app.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="Application Test Suite">
            <directory suffix="Test.php">./tests</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">./app</directory>
        </whitelist>
    </filter>
    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="CACHE_DRIVER" value="array"/>
        <env name="QUEUE_CONNECTION" value="sync"/>
    </php>
</phpunit>

在其 bootstrap/app.php中指定了autoload和一些初始化操作。

命令行参数

绝大部分时候我们可能常用来测试某个方法,因此只需要执行测试文件中的某个方法即可,而不是整个文件。这就需要了解 phpunit 的命令行参数了。文档:https://phpunit.readthedocs.io/en/8.5/textui.html

phpunit --help

...

Test Selection Options:
  --filter <pattern>          Filter which tests to run
  --testsuite <name>          Filter which testsuite to run
  --group <name>              Only runs tests from the specified group(s)
  --exclude-group <name>      Exclude tests from the specified group(s)
  --list-groups               List available test groups
  --list-suites               List available test suites
  --list-tests                List available tests
  --list-tests-xml <file>     List available tests in XML format
  --test-suffix <suffixes>    Only search for test in files with specified suffix(es). Default: Test.php,.phpt

...

Test Selection Options模块参数就是选择执行哪些测试方法。

最简单的方式就是 --filter 直接填上要执行的方法名,当然它毕竟是一个正则匹配,这取决于你的方法命名是否唯一。

phpunit  --filter "testCanBeUsedAsString"  tests/EmailTest

在这里插入图片描述

总结
  1. 针对类 Class 的测试写在类 ClassTest中。
  2. ClassTest继承自 PHPUnit\Framework\TestCase
  3. 测试都是命名为 test* 的公用方法。也可以在方法的文档注释块中使用 @test 标注将其标记为测试方法。
注意

在使用 phpunit 的过程中你大概率会遇到版本的问题,正如我上面所说,大部分框架都会将 phpunit 集成过来,而它集成的 phpunit 是哪个版本呢,和你本地全局安装的 phpunit 是否能兼容呢?

查看全局的 phpunit 版本:phpunit --version
查看项目中的 phpunit 版本:进入到项目 vendor/bin/phpunit --version
比如我全局的是 8.5.14,而项目的是 7.5.20,当我使用全局命令执行测试会报错:

> phpunit tests/DzRead3Test

In TestRunner.php line 155:
                                                                                                                                                                                     
  Argument 3 passed to PHPUnit\TextUI\TestRunner::doRun() must be of the type boolean, array given, called in D:\composer\vendor\phpunit\phpunit\src\TextUI\Command.php on line 23

而实际上 phpunit 经常会出现版本不兼容的情况。于是你应该使用项目中的 phpunit 命令。

> vendor/bin/phpunit tests/DzRead3Test
PHPUnit 7.5.20 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 785 ms, Memory: 10.00 MB

OK (1 test, 1 assertion)

或者将全局 phpunit 降级

> composer global require phpunit/phpunit 7.5.20 --with-all-dependencies

> phpunit --version
PHPUnit 7.5.20 by Sebastian Bergmann and contributors.

> phpunit tests/DzRead3Test
PHPUnit 7.5.20 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 1.83 seconds, Memory: 10.00 MB

OK (1 test, 1 assertion)
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值