本文目录导读:

在 PHP 中给代码加测试,最主流的方式是使用 PHPUnit,以下是完整的实践指南:
安装 PHPUnit
使用 Composer 安装(推荐)
# 全局安装 composer global require phpunit/phpunit # 或项目内安装(开发环境) composer require --dev phpunit/phpunit
基础测试示例
待测试的类文件 Calculator.php
<?php
class Calculator {
public function add($a, $b) {
return $a + $b;
}
public function divide($a, $b) {
if ($b === 0) {
throw new InvalidArgumentException('Division by zero');
}
return $a / $b;
}
}
对应的测试文件 CalculatorTest.php
<?php
use PHPUnit\Framework\TestCase;
class CalculatorTest extends TestCase {
private $calculator;
// 每个测试方法执行前运行
protected function setUp(): void {
$this->calculator = new Calculator();
}
// 测试加法
public function testAdd() {
$result = $this->calculator->add(2, 3);
$this->assertEquals(5, $result);
}
// 测试除法正常情况
public function testDivide() {
$result = $this->calculator->divide(10, 2);
$this->assertEquals(5, $result);
}
// 测试除零异常
public function testDivideByZero() {
$this->expectException(InvalidArgumentException::class);
$this->calculator->divide(10, 0);
}
}
运行测试
# 运行单个测试文件 vendor/bin/phpunit tests/CalculatorTest.php # 运行目录下所有测试 vendor/bin/phpunit tests/ # 运行特定方法 vendor/bin/phpunit --filter testAdd tests/CalculatorTest.php # 输出详细信息 vendor/bin/phpunit --testdox tests/
常用断言方法
// 数值断言
$this->assertEquals(4, $result); // 值相等
$this->assertSame(4, $result); // 值和类型都相等
$this->assertGreaterThan(3, $result); // 大于
$this->assertLessThan(5, $result); // 小于
// 布尔断言
$this->assertTrue($condition);
$this->assertFalse($condition);
// 数组断言
$this->assertCount(3, $array);
$this->assertContains('value', $array);
$this->assertArrayHasKey('key', $array);
// 对象断言
$this->assertInstanceOf(ClassName::class, $object);
$this->assertNull($object);
$this->assertNotNull($object);
// 字符串断言
$this->assertStringContainsString('substring', $string);
$this->assertMatchesRegularExpression('/pattern/', $string);
数据提供器(Data Providers)
class CalculatorTest extends TestCase {
/**
* @dataProvider additionProvider
*/
public function testAdd($a, $b, $expected) {
$calculator = new Calculator();
$this->assertEquals($expected, $calculator->add($a, $b));
}
public function additionProvider() {
return [
'positive numbers' => [1, 2, 3],
'zero' => [0, 0, 0],
'negative numbers' => [-1, -2, -3],
'decimals' => [1.5, 2.5, 4.0],
];
}
}
测试异常
class UserServiceTest extends TestCase {
public function testInvalidEmail() {
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid email format');
$userService = new UserService();
$userService->createUser('invalid-email');
}
public function testDatabaseError() {
$mockDb = $this->createMock(PDO::class);
$mockDb->method('query')
->will($this->throwException(new PDOException()));
$this->expectException(PDOException::class);
// 测试代码...
}
}
模拟对象(Mocking)
use PHPUnit\Framework\TestCase;
class UserControllerTest extends TestCase {
public function testGetUser() {
// 创建模拟对象
$userRepository = $this->createMock(UserRepository::class);
// 配置模拟行为
$userRepository->expects($this->once())
->method('findById')
->with(1)
->willReturn(new User(['id' => 1, 'name' => 'John']));
// 注入模拟对象
$controller = new UserController($userRepository);
$result = $controller->getUser('1');
$this->assertEquals('John', $result['name']);
}
}
清理数据库测试数据
class UserRepositoryTest extends TestCase {
private $db;
protected function setUp(): void {
$this->db = new PDO('sqlite::memory:');
$this->db->exec('CREATE TABLE users (id INT, name TEXT)');
}
protected function tearDown(): void {
$this->db->exec('DROP TABLE users');
}
public function testUserInsert() {
$this->db->exec("INSERT INTO users VALUES (1, 'John')");
$result = $this->db->query("SELECT * FROM users WHERE id=1");
$this->assertEquals('John', $result->fetch()['name']);
}
}
phpunit.xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
colors="true"
stopOnFailure="false">
<!-- 测试套件路径 -->
<testsuites>
<testsuite name="Application Test Suite">
<directory>./tests</directory>
</testsuite>
</testsuites>
<!-- 代码覆盖率 -->
<coverage>
<include>
<directory>./src</directory>
</include>
</coverage>
</phpunit>
最佳实践建议
- 测试命名规则:测试文件以
Test.php方法以test开头 - 一个方法只测一件事:保持测试单一职责
- 使用数据提供器:减少重复测试代码
- 测试真实场景:不要过度使用 mock,重要的逻辑要用真实对象
- 运行所有测试:CI/CD 里配置自动运行测试
- 代码覆盖率:定期检查覆盖率指标
# 生成代码覆盖率报告 vendor/bin/phpunit --coverage-html build/coverage
这样可以直观地看到哪些代码没有测试覆盖。
其他测试框架
- PHPSpec:行为驱动开发(BDD)风格
- Codeception:功能测试和验收测试
- Pest:简洁优雅的测试框架
推荐先学习 PHPUnit,这是最主流的选择,然后根据需求学习其他框架。