本文目录导读:

在 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 即可。
常见问题与避坑
- 命名空间不对:确保
src/和tests/下类的命名空间与 PSR-4 自动加载规则匹配。 - 私有方法无法直接测试:通过公共接口测试或使用反射(不推荐)。
- 静态方法难以 Mock:可将静态方法封装为实例方法,或使用 Mockery 库。
- 测试对象有构造参数:在
setUp()中创建真实对象,或者使用getMockBuilder部分模拟。
其他测试工具(补充)
- Mockery:更强大的 Mock 框架。
- Behat:行为驱动开发(BDD)测试。
- Laravel 专用:
RefreshDatabase、actingAs等测试辅助功能(如果你用 Laravel)。
总结流程
- 写代码 → 2. 写测试(测试目标生产代码) → 3. 运行测试 → 4. 查看覆盖率 → 5. 重构优化(确保测试通过)
单元测试的核心原则是:快速、隔离、可重复、自动化,从简单的纯函数开始测试,逐渐扩展到涉及外部依赖的复杂场景,刚开始可能会觉得耗时,但长期来看能显著减少回归 bug 并提高代码质量。