PHP 怎么单元化

wen PHP项目 3

本文目录导读:

PHP 怎么单元化

  1. 环境准备与安装
  2. 编写第一个测试用例
  3. 运行测试
  4. 核心断言方法(常用)
  5. 高级特性与最佳实践
  6. 项目配置(phpunit.xml)
  7. 常见问题与避坑
  8. 其他测试工具(补充)
  9. 总结流程

在 PHP 中进行单元测试,最主流的方案是使用 PHPUnit,下面是一个从零开始的完整指南,涵盖安装、编写、运行和最佳实践。


环境准备与安装

你需要确保系统安装了 Composer(PHP 的依赖管理工具)。

使用 Composer 安装 PHPUnit

在项目根目录打开终端,运行:

composer require --dev phpunit/phpunit

这会在 vendor/bin/ 目录下生成 phpunit 可执行文件。


编写第一个测试用例

第一步:准备被测试的类

src/ 目录下创建一个简单的计算器类:

<?php
// src/Calculator.php
namespace App;
class Calculator
{
    public function add(int $a, int $b): int
    {
        return $a + $b;
    }
    public function divide(int $a, int $b): float
    {
        if ($b === 0) {
            throw new \InvalidArgumentException("除数不能为零");
        }
        return $a / $b;
    }
}

第二步:创建测试类

tests/ 目录下创建对应的测试文件(命名规范:类名Test.php):

<?php
// tests/CalculatorTest.php
namespace App\Tests;
use App\Calculator;
use PHPUnit\Framework\TestCase;
class CalculatorTest extends TestCase
{
    private Calculator $calculator;
    // 在每个测试方法运行前执行(初始化)
    protected function setUp(): void
    {
        parent::setUp();
        $this->calculator = new Calculator();
    }
    // 测试加法
    public function testAdd(): void
    {
        $result = $this->calculator->add(5, 3);
        $this->assertEquals(8, $result);
    }
    // 测试异常情况
    public function testDivideByZeroThrowsException(): void
    {
        $this->expectException(\InvalidArgumentException::class);
        $this->calculator->divide(10, 0);
    }
    // 测试带数据提供器的用例(推荐)
    #[DataProvider('additionProvider')]
    public function testAddWithDataProvider(int $a, int $b, int $expected): void
    {
        $result = $this->calculator->add($a, $b);
        $this->assertEquals($expected, $result);
    }
    // 数据提供器:提供多组测试数据
    public static function additionProvider(): array
    {
        return [
            [1, 2, 3],
            [10, -5, 5],
            [-3, -7, -10],
            [100, 250, 350]
        ];
    }
}

运行测试

运行全部测试

./vendor/bin/phpunit

运行特定文件

./vendor/bin/phpunit tests/CalculatorTest.php

运行特定方法

./vendor/bin/phpunit --filter testAdd tests/CalculatorTest.php

输出测试覆盖率

./vendor/bin/phpunit --coverage-text

注意:需要安装 Xdebug 或 PCOV 扩展才能查看覆盖率。


核心断言方法(常用)

断言方法
assertEquals 值相等(宽松比较)
assertSame 值相等且类型一致(严格比较)
assertTrue / assertFalse 布尔值验证
assertNull 是否为 null
assertInstanceOf 对象类型
assertCount 数组元素个数
assertStringContainsString 字符串包含某子串
expectException 预期抛出异常

高级特性与最佳实践

1 测试隔离

每次测试运行前,PHPUnit 都会重新创建测试类实例,但如果有依赖外部资源(如数据库、文件),需要确保测试之间互不影响。

2 模拟对象(Mock)

用于测试依赖外部服务的类,测试一个发送短信的服务:

$smsService = $this->createMock(SmsService::class);
$smsService->expects($this->once())
           ->method('send')
           ->with($this->stringContains('验证码'))
           ->willReturn(true);
$orderService = new OrderService($smsService);
$orderService->placeOrder(); // 内部会调用短信服务

3 测试数据库(示例)

对于需要数据库的测试,通常使用 SQLite 内存数据库或 Mock 数据库层。

4 常用注解(Attributes)

  • #[Test]:显式声明测试方法
  • #[DataProvider(‘方法名’)]:把多组数据注入到测试方法
  • #[Depends(‘testMethodName’)]:指定测试方法依赖关系
  • #[Group(‘名称’)]:给测试分组(用于选择性运行)

项目配置(phpunit.xml)

在项目根目录创建 phpunit.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
         colors="true"
         testdox="true"
         failOnWarning="true">
    <testsuites>
        <testsuite name="Application Test Suite">
            <directory>tests</directory>
        </testsuite>
    </testsuites>
    <source>
        <include>
            <directory>src</directory>
        </include>
    </source>
</phpunit>

然后直接运行 ./vendor/bin/phpunit 即可。


常见问题与避坑

  1. 命名空间不对:确保 src/tests/ 下类的命名空间与 PSR-4 自动加载规则匹配。
  2. 私有方法无法直接测试:通过公共接口测试或使用反射(不推荐)。
  3. 静态方法难以 Mock:可将静态方法封装为实例方法,或使用 Mockery 库。
  4. 测试对象有构造参数:在 setUp() 中创建真实对象,或者使用 getMockBuilder 部分模拟。

其他测试工具(补充)

  • Mockery:更强大的 Mock 框架。
  • Behat:行为驱动开发(BDD)测试。
  • Laravel 专用RefreshDatabaseactingAs 等测试辅助功能(如果你用 Laravel)。

总结流程

  1. 写代码 → 2. 写测试(测试目标生产代码) → 3. 运行测试 → 4. 查看覆盖率 → 5. 重构优化(确保测试通过)

单元测试的核心原则是:快速、隔离、可重复、自动化,从简单的纯函数开始测试,逐渐扩展到涉及外部依赖的复杂场景,刚开始可能会觉得耗时,但长期来看能显著减少回归 bug 并提高代码质量。

抱歉,评论功能暂时关闭!