引言:为什么 PHP 测试不是可选项,而是标准
在现代 PHP 开发中,测试早已不再是“锦上添花”,而是生产项目的必要环节。没有测试覆盖的代码,你可能会将错误发布到生产环境,这可能会给企业带来金钱和声誉上的损失。在本文中,我们将探讨三个测试级别:unit(单元测试)、integration(集成测试)和 e2e(端到端测试)。你将了解它们的区别、使用哪些工具(PHPUnit、Pest、Laravel Dusk、Codeception),以及如何为你的 PHP 项目构建正确的测试策略。
1. 单元测试:测试隔离的代码单元
单元测试在隔离外部依赖(数据库、API、文件系统)的情况下,测试单个类、函数或方法的工作情况。这是最快、成本最低的测试类型。PHP 中的主要工具是 PHPUnit(事实标准)及其“时尚”替代品 Pest。
1.1. 安装 PHPUnit 并编写第一个测试
通过 Composer 安装 PHPUnit:
composer require --dev phpunit/phpunit
为一个简单的计算器类创建测试:
<?php
// src/Calculator.php
class Calculator {
public function add(int $a, int $b): int {
return $a + $b;
}
}
// tests/CalculatorTest.php
use PHPUnit\\Framework\\TestCase;
class CalculatorTest extends TestCase {
public function testAddition(): void {
$calc = new Calculator();
$result = $calc->add(2, 3);
$this->assertEquals(5, $result);
}
}
运行测试:
./vendor/bin/phpunit tests
1.2. 使用 Mock 对象
为了隔离被测试的类,我们使用 mock 来“替换”其依赖项。以用户仓库为例:
<?php
class UserService {
private UserRepository $repo;
public function __construct(UserRepository $repo) {
$this->repo = $repo;
}
public function getUserName(int $id): string {
$user = $this->repo->findById($id);
return $user ? $user->getName() : 'Guest';
}
}
// 在测试中
$mockRepo = $this->createMock(UserRepository::class);
$mockRepo->method('findById')
->with(1)
->willReturn(new User('Alice'));
$service = new UserService($mockRepo);
$this->assertEquals('Alice', $service->getUserName(1));
最佳实践: 为所有业务逻辑编写单元测试。它们应在几秒内执行完毕,且不依赖于环境。
2. 集成测试:测试与数据库、API 和外部服务的交互
集成测试检查你的类如何与真实组件(如数据库、缓存、外部 HTTP 服务)协同工作。它们比单元测试慢,但能让你更有信心系统作为一个整体正常运行。
2.1. 测试数据库操作(Laravel + RefreshDatabase)
如果你使用 Laravel,借助 RefreshDatabase trait,集成测试会变得非常简单:
<?php
namespace Tests\\Feature;
use Tests\\TestCase;
use Illuminate\\Foundation\\Testing\\RefreshDatabase;
class UserRegistrationTest extends TestCase {
use RefreshDatabase;
public function test_user_can_register(): void {
$response = $this->post('/register', [
'name' => 'John',
'email' => 'john@example.com',
'password' => 'secret123',
]);
$response->assertStatus(302); // 成功后重定向
$this->assertDatabaseHas('users', [
'email' => 'john@example.com'
]);
}
}
RefreshDatabase trait 会在测试前自动迁移数据库,并在测试后回滚所有更改。